﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using System.Data.OleDb;

namespace NAntExt.Tasks
{
    /// <summary>
    /// The schtasks will continuously query a task for it to end and sleep in between.
    /// </summary>
    [TaskName("schtasks-wait-while-running")]
    public class SchtasksWaitWhileRunningTask : SchtasksTaskBase
    {

        private TimeSpan _queryFrequency = System.TimeSpan.FromSeconds(3);

        /// <summary>
        /// The length of time to sleep in between requests to query on the status of
        /// the task. Default is 3 seconds.
        /// </summary>
        [TaskAttribute("queryFrequency")]
        [StringValidator(AllowEmpty = false)]
        public string QueryFrequency
        {
            get
            {
                return _queryFrequency.ToString();
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    _queryFrequency = TimeSpan.Zero;
                }
                else
                {
                    _queryFrequency = TimeSpan.Parse(value);
                }
            }
        }
        
        /// <summary>
        /// Defines the default success exit code returned by the program. The
        /// value is zero.
        /// </summary>
        public const string DefaultSuccessExitCodes = "0";

        private string _successExitCodes = DefaultSuccessExitCodes;

        /// <summary>
        /// The comma seperated list of valid exit codes from the scheduled task.
        /// </summary>
        /// <value>
        /// The comma seperated list of valid exit codes.
        /// </value>
        /// <remarks>
        /// Defaults to simply zero.
        /// </remarks>
        [TaskAttribute("successexitcodes")]
        public virtual string SuccessExitCodes
        {
            get { return (_successExitCodes != null) ? _successExitCodes : DefaultSuccessExitCodes; }
            set { _successExitCodes = value; }
        }

        private TimeSpan _retryTimeout = System.TimeSpan.FromSeconds(15);

        /// <summary>
        /// The length of time to keep trying to poll for success after Ready is received. This is
        /// to compensate for weird result codes returned from schtasks immediately after it finishes.
        /// Default is 15 seconds.
        /// </summary>
        [TaskAttribute("retryTimeout")]
        [StringValidator(AllowEmpty = false)]
        public string RetryTimeout
        {
            get
            {
                return _retryTimeout.ToString();
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    _retryTimeout = TimeSpan.Zero;
                }
                else
                {
                    _retryTimeout = TimeSpan.Parse(value);
                }
            }
        }

        /// <summary>
        /// The property name to set the result of the operation.
        /// </summary>
        [TaskAttribute("resultProperty")]
        [StringValidator(AllowEmpty = true)]
        public string ResultProperty { get; set; }

        /// <summary>
        /// Entry point for the task.
        /// </summary>
        protected override void ExecuteTask()
        {
            try
            {

                var argumentsBuilder = new System.Text.StringBuilder();

                argumentsBuilder.Append("/Query");

                if (!string.IsNullOrEmpty(this.Server))
                    argumentsBuilder.AppendFormat(" /S \"{0}\"", this.Server);
                
                if (!string.IsNullOrEmpty(this.TaskName))
                    argumentsBuilder.AppendFormat(" /TN \"{0}\"", this.TaskName);

                var arguments = argumentsBuilder.ToString();

                var retryEndTime = DateTime.Now.Add(_retryTimeout);
                bool? lastResultSucceed = null;
                string lastResultFailureMessage = "Unknown error occurred.";
                int lastResult = -1;

                // keep retrying until we have a value OR an exception is thrown
                while (retryEndTime >= DateTime.Now
                    && (!lastResultSucceed.HasValue || !lastResultSucceed.Value))
                {
                    var statusQueryResult = this.ExecuteSchtasks(arguments + " /V /FO LIST");
                    var statusQueryMatch = Regex.Match(statusQueryResult, @"Status:\s*(?<Status>.+)\nLogon Mode:\s*(?<LogonMode>.+)\nLast Run Time:\s*(?<LastRunTime>.+)\nLast Result:\s*(?<LastResult>\d+)");
                    if (statusQueryMatch.Success)
                    {
                        var statusGroup = statusQueryMatch.Groups["Status"];
                        var status = statusGroup.Value.Trim();
                        var lastRunTimeGroup = statusQueryMatch.Groups["LastRunTime"];
                        var lastRunTime = lastRunTimeGroup.Value.Trim();
                        var lastResultGroup = statusQueryMatch.Groups["LastResult"];
                        var lastResultString = lastResultGroup.Value.Trim();
                        if (status == "Ready" && int.TryParse(lastResultString, out lastResult) && _successExitCodes.Split(',').ToList().Contains(lastResultString))
                        {
                            lastResultSucceed = true;
                            Log(Level.Info, "SUCCESS: Task {0} on server {1} completed with last result time {2} exited and last result {3}.", this.TaskName, this.Server, lastRunTimeGroup.Value, lastResultGroup.Value);
                            break;
                        }
                        else
                        {
                            if (status == "Running" || lastResult == 267009 || lastResult == 267045)
                            {
                                // still running reset the retryEndTime since we haven't completed yet
                                retryEndTime = DateTime.Now.Add(_retryTimeout);

                                if (string.IsNullOrEmpty(this.Server))
                                {
                                    Log(Level.Info, "WAITING: Task {0} is running.", this.TaskName);
                                }
                                else
                                {
                                    Log(Level.Info, "WAITING: Task {0} on server {1} is running.", this.TaskName, this.Server);
                                }
                            }
                            else
                            {
                                lastResultSucceed = false;
                                lastResultFailureMessage = string.Format("Task {0} on server {1} completed with last result time {2} exited and last result {3}. Task failed due to result not being {4}. Status was {5}.", this.TaskName, this.Server, lastRunTime, lastResultString, _successExitCodes, status);
                                break;
                            }

                            // sleep for a bit so we don't query to much
                            System.Threading.Thread.Sleep(this._queryFrequency);
                        }
                    }
                    else
                    {
                        // this is bad if we get here because the format of the result has probably changed or we have a bad parsing error of the result

                        lastResultSucceed = false;
                        lastResultFailureMessage = string.Format("Task {0} on server {1} is in an unknown state.", this.TaskName, this.Server);
                        Log(Level.Info, string.Format("RECHECKING: {0} Retrying to ensure status is real.", lastResultFailureMessage));

                        // sleep for a bit so we don't query to much
                        System.Threading.Thread.Sleep(this._queryFrequency);
                    }
                }

                if (!string.IsNullOrEmpty(this.ResultProperty))
                {
                    // if the result property is set then assign it to the property now that we are done.

                    if (this.Properties.Contains(this.ResultProperty))
                    {
                        this.Properties.Add(this.ResultProperty, lastResult.ToString());
                    }
                    else
                    {
                        this.Properties[this.ResultProperty] = lastResult.ToString();
                    }
                }

                // if the loop finishes and we have a bad value then let's report a failure
                if (lastResultSucceed.HasValue 
                    && !lastResultSucceed.Value)
                {
                    string failureMessageLog = string.Format("FAILED: {0}", lastResultFailureMessage);
                    if (this.FailOnError)
                    {
                        throw new ApplicationException(failureMessageLog);
                    }
                    else
                    {
                        this.Log(Level.Warning, failureMessageLog);
                    }
                }
            }
            catch (Exception ex)
            {
                this.ThrowOrLog(this.Location, ex);
            }
        }
                
    }
}
